package video.com.service.impl;


import cn.hutool.crypto.digest.DigestUtil;

import com.Exception.GlobalException;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.constant.ResponseDataConstant;
import com.contant.RedisConstantPrefix;
import com.google.gson.Gson;
import com.responseData.ResponseData;

import com.utils.GenerateNum;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import video.com.entity.VideoFile;
import video.com.mapper.FileMapper;
import video.com.service.VideoFileService;
import video.com.transportEntity.vo.FileUploadVo;
import video.com.utils.IdCreator;
import video.com.utils.RedisUtils;
import video.com.utils.UserInfoUtils;
import video.com.utils.VideoFormatUtil;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author xwj
 * @since 2023-11-11
 */
@Slf4j
@Service
public class VideoFileServiceImpl extends ServiceImpl<FileMapper, VideoFile> implements VideoFileService {


    @Value("${file.path}")
    private String filePath;

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    /**
     * 上传视频文件，文件大小小于分片数量不放入redis 上传失败即失败
     * 文件大小大于分片数量时 每次上传以恶搞分片，将已上传的分片记录在redis中
     * 文件整个上传完成后 将得到的文件与前端文件生成的sha256进行对比，相同则删除redis中缓存，并删除文件分片
     * @param file
     * @param vo
     * @return
     * @throws Exception
     */
    @Override
    public Object uploadVideo(MultipartFile file, FileUploadVo vo) throws Exception {

        String fileId = null;

        //从ThreadLocal 中获取登录用户的userId
        String userId = null;
        try{
            userId = UserInfoUtils.getUserInfo().get("userId").toString();
        }catch (Exception e){
            e.printStackTrace();
            throw new GlobalException(ResponseData.unusualCustomize(ResponseDataConstant.USER_USUAL,"获取用户信息失败"));
        }

        if(vo.getFileHash() == null){
            throw new GlobalException(ResponseData.error("文件未上传"));
        }

        try{
            int pos = vo.getOriginName().lastIndexOf(".");
            //文件保存到服务器路径
            String fileDir = vo.getOriginName().substring(0, pos);

            File localFile = new File(filePath + userId + "/" + vo.getFileHash() + "/" + fileDir + "/"  +  vo.getChunkName());

            //文件路径涉及的文件夹是否存在
            if(!localFile.exists()){
                localFile.mkdirs();
            }

            //文件转换
            file.transferTo(localFile);

            //完整文件保存路径
            String videoUrl = filePath + userId + "/" + vo.getFileHash() + "/" + fileDir + "/"  +  vo.getOriginName();
            //文件是否被切片
            if(vo.getTotal() != 1){

                //保存当前切片信息到redis
                RedisUtils.redis.opsForSet().add(vo.getFileHash(),vo.getCurrentChunk());
                RedisUtils.redis.expire(vo.getFileHash(),10, TimeUnit.MINUTES);

                //校验文件是否全部上传 准备文件组装
                fileId = checkFileCorrect(vo, videoUrl);
            }else{
                //文件不满足分片
                String fileHash = DigestUtil.sha256Hex(localFile);

                if(fileHash.equals(vo.getFileHash())){
                    VideoFile videoFile = new VideoFile();
                    videoFile.setFileId(GenerateNum.generateOrder());

                    //ffmpeg 生成m3u8文件
                    String m3u8 = VideoFormatUtil.formatVideo(videoUrl, userId);
                    videoFile.setFileUrl(m3u8);

                    fileId = videoFile.getFileId();

//                    this.save(videoFile);
                    Gson gson = new Gson();
                    RedisUtils.redis.opsForValue().set(RedisConstantPrefix.VIDEO_FILE_PREFIX+fileId,gson.toJson(videoFile),10, TimeUnit.MINUTES);


                }
            }

        }catch (IOException e){
            e.printStackTrace();
            throw new GlobalException(ResponseData.error("文件保存失败"));
        }

        return fileId;
    }

    /**
     * 上传文件封面
     * @param file
     * @param vo
     * @return
     * @throws IOException
     * @throws Exception
     */
    @Override
    public Object uploadVideoCover(MultipartFile file, FileUploadVo vo) throws IOException, Exception {
        String fileId = null;

        //从ThreadLocal 中获取登录用户的userId
        String userId = null;
        try{
            userId = UserInfoUtils.getUserInfo().get("userId").toString();
        }catch (Exception e){
            e.printStackTrace();
            throw new GlobalException(ResponseData.unusualCustomize(ResponseDataConstant.USER_USUAL,"获取用户信息失败"));
        }


        if(vo.getFileHash() == null){
            throw new GlobalException(ResponseData.error("文件未上传"));
        }

        try {

            String fileDir = vo.getOriginName().substring(0, vo.getOriginName().lastIndexOf("."));

            File localFile = new File(filePath + userId + "/" +  vo.getFileHash() + "/" + fileDir + "/" + vo.getChunkName());

            if (!localFile.exists()) {
                localFile.mkdirs();
            }

            file.transferTo(localFile);

            VideoFile videoFile = new VideoFile();
            videoFile.setFileId(GenerateNum.generateOrder());
            videoFile.setFileUrl(filePath + userId + "/" + vo.getFileHash() + "/" + fileDir + "/"  +  vo.getChunkName());

//            this.save(videoFile);
            fileId = videoFile.getFileId();

            Gson gson = new Gson();
            RedisUtils.redis.opsForValue().set(RedisConstantPrefix.VIDEO_COVER_PREFIX + fileId,gson.toJson(videoFile),10, TimeUnit.MINUTES);

        }catch (IOException e){
            e.printStackTrace();
            throw new GlobalException(ResponseData.error("文件转换异常"));
        }

        return fileId;
    }

    /**
     *
     * @param fileIds 需删除的id列表
     * @return
     */
    @Override
    public void removeBatch(String... fileIds) {

        SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);

        FileMapper mapper = sqlSession.getMapper(FileMapper.class);

        try{

            for(int i = 0 ; i< fileIds.length ; i++){
                mapper.deleteByFileId(fileIds[i]);

                if((i + 1) % 20000 == 0){
                    sqlSession.commit();
                    sqlSession.clearCache();
                }

            }

            sqlSession.commit();
        }catch (Exception e){
            sqlSession.rollback();
        }

    }

    /**
     * 检验文件正确性
     * @param vo
     * @return
     * @throws IOException
     */
    private String checkFileCorrect(FileUploadVo vo, String videoUrl) throws Exception {

        String fileId = null;

        //获取文件已上传切片信息
        Set values = RedisUtils.redis.opsForSet().members(vo.getFileHash());

        //文件未完全上传
        if(values.size() != vo.getTotal()){
            return fileId;
        }

        //文件开始组装
        FileOutputStream out = null;
        try{

            out = new FileOutputStream(videoUrl);

            int len = 0;
            byte[] bytes = new byte[10240];
            FileInputStream in = null;

            for(int i = 1 ; i <= vo.getTotal(); i ++){

                in = new FileInputStream(videoUrl + i);

                while((len = in.read(bytes)) > 0){
                    out.write(bytes,0,len);
                }

                in.close();
            }

        }catch (IOException e){
            e.printStackTrace();
        }finally {
            if(out != null){
                out.close();
            }
        }

        File targetFile = new File(videoUrl);

        //获取文件组装后的文件sha256
        byte[] hash = DigestUtil.sha256(targetFile);
        StringBuilder hexString = new StringBuilder();
        for (byte b : hash) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) hexString.append('0');
            hexString.append(hex);
        }

        String sha256Hex = hexString.toString();

        //若文件sha256相同就删除文件分片
        if(sha256Hex.equals(vo.getFileHash())){
            for(int i = 0 ; i < vo.getTotal(); i ++){
                File temp =  new File(videoUrl + (i + 1));
                temp.delete();

            }

            //保存文件到数据库
            VideoFile videoFile = new VideoFile();
            String id = GenerateNum.generateOrder();
            videoFile.setFileId(id);
            String s = VideoFormatUtil.formatVideo(videoUrl, id);
            videoFile.setFileUrl(s);
            try{
//                this.save(videoFile);
                Gson gson = new Gson();
                RedisUtils.redis.opsForValue().set(RedisConstantPrefix.VIDEO_FILE_PREFIX + id,gson.toJson(videoFile),10, TimeUnit.MINUTES);

            }catch (Exception e){
                e.printStackTrace();
                throw new GlobalException(ResponseData.error(e.getMessage()));
            }finally {
                RedisUtils.remove(vo.getFileHash());
            }

            fileId = videoFile.getFileId();
        }
        return fileId;
    }

    @Override
    public void insertBatch(VideoFile[] videoFiles){
        this.saveBatch(Arrays.asList(videoFiles));
    }
}
